#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <errno.h>

#include "sans.h"
#include "garbagecollector.h"

#define MAX_SPACE (1<<23)

u space_used = 0;
sdata from_space[MAX_SPACE];
sdata to_space[MAX_SPACE];

sdata *cur_space = from_space;

sdata heap_ref(u ref) {
  if (ref < space_used) {
    return cur_space[ref];
  }
  else {
    log_err("garbagecollector/heap_ref: %ld out of bounds %ld\n", ref, space_used);
  }
}

void heap_set(u ref, sdata x) {
  if (ref < space_used) {
    cur_space[ref] = x;
  }
  else {
    log_err("garbagecollector/heap_set: %ld out of bounds %ld\n", ref, space_used);
  }
}

sdata cons(sdata x, sdata y) {
  u place;

  // BUGFIX: This used to check space_used < MAX_SPACE
  // which was wrong
  if (space_used + 2 <= MAX_SPACE) {
    place = space_used;
    cur_space[space_used++] = x;
    cur_space[space_used++] = y;
    return (sdata){.tag = tcons, .data.value = place};
  }
  else {
    // TODO: gc
    log_err("garbagecollector/cons: ran out of memory\n");
  }
}

u allocate_string_buff(u len) {
  u place;

  // https://www.securecoding.cert.org/confluence/display/c/INT31-C.+Ensure+that+integer+conversions+do+not+result+in+lost+or+misinterpreted+data
  if (len > LONG_MAX) {
    log_err("garbagecollector/allocate_string: %lu is larger than s can hold\n", len);
  }
  
  // we want to reserve 1 + len contiguous cells for our vector
  if (space_used + 1 + len <= MAX_SPACE) {
    place = space_used;
    space_used += 1 + len;
    cur_space[place] = (sdata){.tag = tnumb, .data.ivalue = len};

    return place;
  }
  else {
    // TODO: gc
    log_err("garbagecollector/allocate_string: ran out of memory\n");
  }
}

sdata allocate_string(char* string) {
  u i;
  u len = strlen(string);
  u place = allocate_string_buff(len);

  for (i = 0; i < len; i++) {
    cur_space[place + 1 + i] = SDCHAR(string[i]);
  }
  return (sdata){.tag = tstrn, .data.value = place};
}

sdata allocate_vector(u len) {
  u place;
  u i;

  // https://www.securecoding.cert.org/confluence/display/c/INT31-C.+Ensure+that+integer+conversions+do+not+result+in+lost+or+misinterpreted+data
  if (len > LONG_MAX) {
    log_err("garbagecollector/allocate_vector: %lu is larger than s can hold\n", len);
  }
  
  // we want to reserve 1 + len contiguous cells for our vector
  if (space_used + 1 + len <= MAX_SPACE) {
    place = space_used;
    space_used += 1 + len;
    cur_space[place] = (sdata){.tag = tnumb, .data.ivalue = len};

    // prefill the vector with #f to avoid nondeterminism/uninitialized values
    for (i = 0; i < len; i++) {
      cur_space[place + 1 + i] = (sdata){.tag = tbool, .data.value = 0};
    }
    return (sdata){.tag = tvect, .data.value = place};
  }
  else {
    // TODO: gc
    log_err("garbagecollector/allocate_vector: ran out of memory\n");
  }
}

sdata allocate_closure(u len, u label) {
  // [label][vec -->][len,vector elements]
  
  sdata vec = allocate_vector(len+2);
  sdata vec_len;

  u place;
  
  place = vec.data.value;

  // make the vector 2 smaller
  // move the length tag up 2
  vec_len = cur_space[place];
  vec_len.data.ivalue -= 2;
  cur_space[place+2] = vec_len;

  // put a scheme vector down into the heap
  vec.data.value+=2;
  cur_space[place+1] = vec;

  // put the label down
  cur_space[place] = (sdata){.tag = tlabl, .data.ivalue = label };

  return (sdata){.tag = tclos, .data.value = place};
}

#include <assert.h>
void bltn_vector_set_bang(sdata ob, sdata idx, sdata val);
void closure_set(sdata cl, u i, sdata v) {
  assert(cl.tag == tclos);
  sdata vec = cur_space[cl.data.value+1];
  bltn_vector_set_bang(vec, SDNUMB(i), v);
}


sdata bltn_closure_env_set_bang(sdata clo, sdata idx, sdata v) {
closure_set(clo, idx.data.value, v);
return clo;
}
